/**
 \file firmware.pde
 \brief Domino (OpenDomo for Arduino)
 ****************************************************************************
 *  Copyright (C) 2010  Oriol Palenzuela <opalenzuela(at)gmail(dot)com>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ****************************************************************************
 \mainpage Domino
 Domino is a multi-purpose driver, designed for creating quick prototypes and
 automated products using a normalized configuration syntax. 

 A detailed explanation of all the supported commands and architecture can be 
 found in the following webpages: 

 -# http://es.opendomo.org/domino (Main page)
 -# http://es.opendomo.org/domino_reference (Reference manual)

 This document is only intended to be useful for the firmware's programmers.
*/

/// Current library version
#define VERSION "version 0.2.4"
#include <SPI.h>
// Library for managing the configuration storage
#include <EEPROM.h>
// Library for using an Ethernet Shield as host connection
#include <Server.h>
#include <Client.h>
#include <Ethernet.h>


#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega328P__)
#define kBoard_PinCount 20
#define kBoard_AnalogCount 6
#elif defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
#define kBoard_PinCount 70
#define kBoard_AnalogCount 16
#endif

/** \page EEPROM EEPROM Map
 The EEPROM in Arduino Duemilanove with ATMEGA328 has 1024Bytes availables. 
 Domino uses this space to store all the configuration variables required, 
 organized in the following segments: 

 -# Ports configuration: starting from the first byte (\ref EMPORTSOFFSET), with 
	10 Bytes (\ref EMPORTSLOT) for each port, it contains the alias and type of 
	each port.
 -# Links: starting after the ports segment (\ref EMLINKSOFFSET) contains the 
	index of two ports and a third byte for the type.
 -# Board information: board name (should be moved to the end of EEPROM)
 -# Advanced port information: starting from \ref EMADVPSOFFSET, contains the 
	data of extended ports (virtual and grouped ports)
 -# Network information: starting from \ref EMNETCFOFFSET, contains 20 bytes 
	with the network information (MAC(6)+IP(4)+GW(4)+MASK(4))

 Note that the configuration map of version 0.1.x of the firmware might differ
 due resource limitations of old ATMEGA chips. 
*/
#define EMPORTSOFFSET 0    //!< Bytes 000-199 Ports configuration
#define EMLINKSOFFSET 400  //!< Bytes 400-499 Links 
#define EMBOARDOFFSET 500  //!< Bytes 500-512 Board information
#define EMANALOGOFFSET 515 //!< Bytes 515-599 Analog port information
/// Advanced features (>512)
#define EMADVPSOFFSET 600  //!< Bytes 600-800 Advanced port information
#define EMNETCFOFFSET 800  //!< Bytes 800-820 Network: MAC(6)+IP(4)+GW(4)+MASK(4)
#define EMPORTSLOT 10      //!< Reserved bytes for each port
//      12345-6-7-8-9-0: nombre-config-cmin-wmin-wmax-cmax (analogicos)
//                       nombre-config-????-????-????-???? (digitales)

#define DELAYCYCLE 100 //!< Delay in each cycle, in milliseconds

/// Configuring the characteristics of the board
#define PINCOUNT  20  //!< Total number of pins (14 digital + 6 analogic)
#define DIGICOUNT 14  //!< Number of digital pins
#define ANALOGPORTS (PINCOUNT-DIGICOUNT) //!< Analog ports count
#define MAXLINKS  10  //!< Maximum number of links
#define MAXTIMERS 30  //!< Maximum number of timers

#define BUFFERSIZE 20 //!< Maximum lenght for the command
#define MAXCHANGES 5  //!< Maximum number of changes allowed per second

#define ANALOG  65  //!< Alias for ANALOG    ! ANALOG  DIGITAL
#define DIGITAL 68  //!< Alias for DIGITAL --+----------------
#define IN      73  //!< Alias for INPUT     !   a       d
#define OUT     79  //!< Alias for OUTPUT    !   A       D
#define OFF      0  //!< Alias for OFF 
#define ON    HIGH  //!< Alias for ON

#define LISTENINGPORT 23 //!< TCP listening port (see \ref Ethernet Shield)

#define MAXVPORTS 10 //!< Maximum number of virtual ports

//! Iterate for each link
#define FOREACHLINK(i)  for (i=0;i<MAXLINKS;i++)
//! Delete the specified link
#define emptyLink(i) {links[i][0]=0; links[i][1]=0; links[i][2]=0;}


// This macros will make a little easier to work with the memory structures
//! Iterate for each port
#define FOREACHPORT(i)  for(i=0;i<PINCOUNT;i++) 
//! Iterate for each virtual port
#define FOREACHVPORT(i) for(i=0;i<MAXVPORTS;i++)
//! Return TRUE if the specified port is enabled 
#define portenabled(n)  (porttype[n]!=0)
//! Return TRUE if the specified port is an INPUT port
#define ISINPUT(n)   ((porttype[n]=='a')||(porttype[n]=='d')||(porttype[n]=='p'))
//! Return TRUE if the specified port is an OUTPUT port
#define ISOUTPUT(n)  ((porttype[n]=='A')||(porttype[n]=='D')||(porttype[n]=='P'))
//! Return TRUE if the specified port is an ANALOG port
#define ISANALOG(n)  ((porttype[n]=='a')||(porttype[n]=='A')) 
//! Return TRUE if the specified port is a DIGITAL port
#define ISDIGITAL(n) ((porttype[n]=='d')||(porttype[n]=='D')) 
//! Return TRUE if the specified port is a PULSE port
#define ISPULSE(n)   ((porttype[n]=='p')||(porttype[n]=='P')) 
//! Return TRUE if the specified port is virtual
#define ISVIRTUAL(n) ((n>PINCOUNT) && (vports[n-PINCOUNT][0]!=0))

// This functions will help with the string processing
//! Return TRUE if word has the 3 specified characters
#define SPELL3(word,c1,c2,c3) ((word[0]==c1) && (word[1]==c2) && (word[2]==c3))
//! Print the specified string to the default buffer (Serial)
#define printstr(txt) Serial.print(txt)
//! Copy the contents of the EEPROM specified region into the string
#define EEPROM2string(offset,lenght,string) for(i=0;i<lenght;i++) \
	{string[i]=EEPROM.read(offset+i);}

// lnk puls1 alarm  
// 012345678901234
//! Copy the first argument into the string
#define GETARG1(str,arg1) arg1[0]=str[4];arg1[1]=str[5];arg1[2]=str[6];\
	arg1[3]=str[7];arg1[4]=str[8];arg1[5]=0;
//! Copy the second argument into the string
#define GETARG2(str,arg2) arg2[0]=str[10];arg2[1]=str[11];arg2[2]=str[12];\
	arg2[3]=str[13];arg2[4]=str[14];arg2[5]=0;
//! Return TRUE if the 6 first bytes of the specified strings are equal
#define strcmp6(s1,s2) ((s1[0]==s2[0]) && (s1[1]==s2[1]) && \
	(s1[2]==s2[2]) && (s1[3]==s2[3]) && (s1[4]==s2[4]) && (s1[5]==s2[5]))
//! Return TRUE if the byte is a valid character (a-z and 0-9)
#define ISVALIDCHAR(a) ((a>=48 && a<=57) || (a>=97 && a<=122))

// Arduino Duemilanove dispone de solo 2KB de SRAM. Optimizar estos valores: 
//! Port type byte (a,d,A,D,p,P,-)
byte porttype[PINCOUNT];      // 20x1  20
//! Port value
byte portvalue[PINCOUNT];     // 20x1  20
//! Number of changes happened in the port during the last second
byte portchanges[PINCOUNT];   // 20x1  20
//! Port name (5 Bytes)
char portname[PINCOUNT][6];   // 20x6 120
//! Link table (3 bytes: port1, port2 and type)
byte links[MAXLINKS][3];      // 30x3  90
//! Timer table
//byte timers[MAXTIMERS][3];    // 30x3  90
unsigned int t_count[DIGICOUNT]; // 14x2 28
unsigned int t_len[DIGICOUNT]; // 14x2 28
//! Virtual port list: the name itself defines the function 
char vports[MAXVPORTS][6];    // 10x6  60
//TOTAL                               420B


// Alert levels for analogic input ports
//              a00   a01   a02   a03   a04   a05
int  wmax[] = { 1024, 1024, 1024, 1024, 1024, 1024}; //!< Warning Max limits 
int  wmin[] = {    0,    0,    0,    0,    0,    0}; //!< Warning Min limits
int  amax[] = { 1024, 1024, 1024, 1024, 1024, 1024}; //!< Alarm Max limits 
int  amin[] = {    0,    0,    0,    0,    0,    0}; //!< Alarm Min limits
char tolr[] = {    0,    0,    0,    0,    0,    0}; //!< Tolerance values

char instruction[BUFFERSIZE];	///< Command received
byte inspos=0;		///< Command byte position
byte echo=0;		///< Flag that defines if the local echo is activated
byte debug=0;		///< Flag that defines if the board operates in debug mode
byte alarmport=0;	///< Alarm port configured (0 if none)
byte pulseport=0;	///< Pulse port configured (0 if none)
int loops=0;		///< Number of loops

byte codeIR[4] = {0, 0, 0, 0}; ///< Code captured by a buffer port

#define DAYLENGHT 86400
/// Registro global de tiempo
unsigned long time = 0;
unsigned int days = 0;
unsigned int seconds = 0;

/// Printf function overloaded to match all the cases
void printf(char *a) {raiseEvent3(a,(char*)" ",(char*)" ");}
void printf(char *a, char *b) {raiseEvent3(a,b,(char*)" ");}
void printf(char *a, char *b, char *c) {raiseEvent3(a,b,c);}
void printf(char *a, char *b, char *c, int d) {raiseEvent4(a,b,c,d);}
void printf(char *a, char *b, char *c, int d, int e) {
  char ret[10];
  itoan(d,ret,4);
  raiseEvent4(a,b,c,d);
}



/** This function print 3 strings to the standard output
 \param lvl Level of the event (D/N/W/E/I)
 \param element Element that generates the event
 \param msg Event description 
 @todo This functions should use a single Serial.println to send the data
*/
void raiseEvent3(char *lvl, char *element, char *msg)
{
  if (debug!=1 && lvl[0]=='D') return; // ommit debugging messages
  Serial.print(lvl); 
  Serial.print(": "); 
  Serial.print(element); 
  Serial.print(" "); 
  Serial.println(msg);  
}
/** Alternate \ref raiseEvent function with an additional numerical value
 \param lvl Level of the event (N/W/E/I)
 \param element Element that generates the event
 \param msg Event description 
 \param val Additional numeric value
*/
void raiseEvent4(char *lvl, char *element, char *msg, int val)
{
  if (debug!=1 && lvl[0]=='D') return; // ommit debugging messages
  Serial.print(lvl); 
  Serial.print(": "); 
  Serial.print(element); 
  Serial.print(" "); 
  Serial.print(msg); 
  Serial.print(" ");
  Serial.println(val,DEC); 
}
/// Temporary!
void raiseEvent5(char *lvl, char *element, char *msg, int val, int num)
{
  if (debug!=1 && lvl[0]=='D') return; // ommit debugging messages
  Serial.print(lvl); 
  Serial.print(": "); 
  Serial.print(element); 
  Serial.print(" "); 
  Serial.print(msg); 
  Serial.print(" ");
  Serial.print(val,DEC); 
  Serial.print(" ");
  Serial.println(num,DEC); 
}

/** Copies a string from one variable to another
 \param dest Destination string
 \param len Length of the string to be copied
 \param src Source string
*/
void strncpy(char *dest, int len, char *src)
{
  byte i;
  for (i=0;i<len;i++)
  {
    dest[i]=src[i];
  }
  dest[i]=0;
}

/** Concats a string from one variable to another
 \param dest Destination string
 \param maxlen Max lenght of the destination string
 \param len Length of the source string
 \param src Source string
*/
boolean strncat(char *dest, int maxlen, int len, char *src)
{
  byte i;
  byte p;
  for (i=0;i<maxlen;i++)
  {
    if (dest[i]==0) {
      p=i; 
      i=maxlen; // Exit the loop
    }
  }  
  for (i=0;i<len; i++)
  {
    if (i+p>maxlen) return -1; 
    dest[i+p]=src[i];
    dest[i+p+1]=0;
  }
  return true;
}

/** Transforms a 5 bytes string into its integer value
 \param str Source string
 \return The integer value
*/
int atoi(char * str) 
{
  int i;
  int retval=0;
  for (i=0;i<5;i++)
  {
    if (str[i]>47 && str[i]<58)
    {
      retval = retval * 10 + (str[i]-48);
    }
  }
  return retval;
}

/** Transforms a integer into a string with lenght LEN
 \param val Integer value
 \param result String where the result will be written
 \param len Lenght of the string
 \return true if the operation was succeeded
*/
boolean itoan(int val, char *result, int len)
{
  int i=val;
  int c=0;

  for (i=0;i<len;i++) result[i]='0'; //Rellenamos con ceros

  while(i>0)  
  {
    c++;
    i /= 10;
  }

  if(c>len) return false; // Too less bytes to represent

  result[len]=0;
  c=len;
  i=val;
  while(i>0)
  {
    int digit = i - 10*(i/10);
    i /= 10;
    result[--c]=digit+48;
  }
  return true;
}

/** Transforms a 2 bytes hexadecimal string into its integer value
  Example: h2d(E,F)=239
 \param a First byte of the source string
 \param b Second byte of the source string
 \return The integer value
*/
byte h2d(char a, char b) 
{
  if (a>=65 && a<=70) a=a-55; else a=a-48;
  if (b>=65 && b<=70) b=b-55; else b=b-48; 
  return (a*16+b);
}

/** Converts an integer value into string and forwards it to the Serial port
 \param val Integer value
*/
void printnum(int val)
{
  char ret[10];
  itoan(val,ret,4);
  Serial.print(ret);
}



/** Set the type and configured characteristics to each port
*/
void resetPorts()
{
  int i;
  FOREACHPORT(i)
  {
	/// @todo Alta impedancia if type = "-"
    if(ISINPUT(i)) 
    {
      pinMode(i, INPUT);
    }
    else if(ISOUTPUT(i))
    {
      pinMode(i, OUTPUT);
    }
    //else if(ISPULSE(i))
    if(ISPULSE(i))
    {
      //pinMode(i, INPUT);
      pulseport=i;
      //noInterrupts();
      if(i==2) attachInterrupt(0,processIR,CHANGE);
      if(i==3) attachInterrupt(1,processIR,CHANGE);
    }
  }

} 

/// Load the configuration from the EEPROM
int loadConfig()
{
  int x;
  int y;

	FOREACHPORT(x)
	{
		for (y=0;y<5;y++)
		{
			portname[x][y]=EEPROM.read(x*EMPORTSLOT+y);
			// If the port name slots use different values than a-z, return error
			if (!ISVALIDCHAR(portname[x][y])) 
			{
				raiseEvent3((char*)"E",portname[x],(char*)"invalid char found");
				return 1; // Abortamos para que se cargue la cfg. por defecto
			}
		}
		portname[x][5]= 0; // Cerramos cadena limpiamente
		porttype[x]  = EEPROM.read(x*EMPORTSLOT+5);
	}
	for (x=0;x<MAXLINKS;x++)
	{
		//Unshift para convertirlo desde ASCII imprimible
		links[x][0] = EEPROM.read(EMLINKSOFFSET+x*3)-65; 
		links[x][1] = EEPROM.read(EMLINKSOFFSET+x*3+1)-65;
		links[x][2] = EEPROM.read(EMLINKSOFFSET+x*3+2);
	}
	for (x=0;x<ANALOGPORTS;x++)
	{
		/// @todo Read 2 bytes per entry
		/*
		amax[x] = EEPROM.read(EMANALOGOFFSET+x*12 + 0);
		amin[x] = EEPROM.read(EMANALOGOFFSET+x*12 + 2);
		wmax[x] = EEPROM.read(EMANALOGOFFSET+x*12 + 4);
		wmin[x] = EEPROM.read(EMANALOGOFFSET+x*12 + 8);
		tolr[x] = EEPROM.read(EMANALOGOFFSET+x*12 + 10);
		*/
	}
	return 0;
}

/// Save the configuration into the EEPROM (see \ref EEPROM Map)
int saveConfig()
{
	int x;
	int y;
	int dir;
	byte b;

	FOREACHPORT(x)
	{
		dir = EMPORTSOFFSET + x * EMPORTSLOT;
		for (y=0;y<5;y++)
		{
			b = portname[x][y];
			EEPROM.write(dir+y,b);
		}
		EEPROM.write(dir+5,porttype[x]);
	}
	for (x=0;x<MAXLINKS;x++)
	{
		//Shift to convert the value in printable ASCII 
		EEPROM.write(EMLINKSOFFSET+x*3+0, links[x][0]+65); 
		EEPROM.write(EMLINKSOFFSET+x*3+1, links[x][1]+65);
		EEPROM.write(EMLINKSOFFSET+x*3+2, links[x][2]);
	}
	for (x=0;x<ANALOGPORTS;x++)
	{
		/// @todo Write 2 bytes per entry
		/*
		EEPROM.write(EMANALOGOFFSET+x*12 + 0, amax[x]);
		EEPROM.write(EMANALOGOFFSET+x*12 + 2, amin[x]);
		EEPROM.write(EMANALOGOFFSET+x*12 + 4, wmax[x]);
		EEPROM.write(EMANALOGOFFSET+x*12 + 8, wmin[x]);
		EEPROM.write(EMANALOGOFFSET+x*12 + 10, tolr[x]);
		*/
	}
	return 0;
}

/** Print the board name to the standard output
 \return Always 0
*/
int printBoardName()
{
  int i;
  char name[6];
  for (i=0;i<5;i++) 
  {
    name[i] = EEPROM.read(EMBOARDOFFSET+i);
    if (name[i]<97 && name[i]>122)
    {
       saveBoardName((char *)"dmino");
       printf((char*)"I",(char*)"board name",(char*)"dmino");
       return 0;
    } 
  }
  name[5]=0;
  printf((char*)"I",(char*)"board name",name);
  return 0;
}

/** Store the specified name of the board in the EEPROM
 \param name New board name (5 Bytes)
 \return Always 0
*/
int saveBoardName(char *name)
{
  int i;
  for (i=0;i<5;i++) 
  {
    EEPROM.write(EMBOARDOFFSET+i,name[i]);
  }
  return 0;
}

/** Set a port's configuration (and name)
 \param id Port id
 \param cfg String with the configuration
 \param name New port name (optional)
 \return 0 if everything went right. Otherwise, -1
*/
int configPort(int id, char *cfg, char *name)
{
  int i;
  if (id<0) return -1; // Ignoramos ID =-1 (puerto no encontrado)
  if (cfg!=NULL)
  {  
    /* Anulamos los rangos de alerta
    emax[id]=100;
    wmax[id]=100;
    emin[id]=0;
    wmin[id]=0;
    */
    porttype[id]='X';
    for (i=0;i<5;i++)
    {
      if (cfg[i]=='p'){
        pulseport=id;
        porttype[id]='P';
      }
      if (cfg[i]=='A') alarmport=id;
      if (cfg[i]=='d') porttype[id]='D';
      if (cfg[i]=='a') porttype[id]='A';
      if (cfg[i]=='i')
      {
        if (porttype[id]=='D') porttype[id]='d';
        if (porttype[id]=='A') porttype[id]='a';
        if (porttype[id]=='P') porttype[id]='p';
      }
    }
  }

  if (name!=NULL)
    strncpy(portname[id],5,name);
    
  return 0;
}  

/** Load the default configuration. 
 Useful if the EEPROM was empty or corrupted or before loading a template.
*/
void loadDefaultConfig()
{  
   configPort( 0,(char *)"-",   (char *)"usbrx"); // RX del USB. Ocupado
   configPort( 1,(char *)"-",   (char *)"usbtx"); // TX del USB. Ocupado
   configPort( 2,(char *)"do",  (char *)"do002"); // (1)
   configPort( 3,(char *)"do",  (char *)"do003"); // (1) (2)
   configPort( 4,(char *)"do",  (char *)"do004"); 
   configPort( 5,(char *)"do",  (char *)"do005"); // (2)
   configPort( 6,(char *)"do",  (char *)"do006"); // (2)
   configPort( 7,(char *)"do",  (char *)"do007");  
   configPort( 8,(char *)"do",  (char *)"do008");
   configPort( 9,(char *)"do",  (char *)"do009"); // (2)
   configPort(10,(char *)"do",  (char *)"do010"); // (2)
   configPort(11,(char *)"do",  (char *)"do011"); // (2)
   configPort(12,(char *)"do",  (char *)"do012"); 
   configPort(13,(char *)"doA", (char *)"do013"); // (3)
   configPort(14,(char *)"-",   (char *)"ai000"); 
   configPort(15,(char *)"-",   (char *)"ai001"); 
   configPort(16,(char *)"-",   (char *)"ai002"); 
   configPort(17,(char *)"-",   (char *)"ai003");  
   configPort(18,(char *)"-",   (char *)"ai004"); 
   configPort(19,(char *)"-",   (char *)"ai005");
   // (1) Estos puertos pueden desencadenar una interrupcion prioritaria   
   // (2) Estos puertos pueden simular una salida analógica con PWM
   // (3) Puerto de alarma por defecto (dispone del LED en placa)
}

/// Initialises the Ethernet Shield (EXPERIMENTAL)
int initNetwork()
{
	/** \page Ethernet Ethernet Shield
	If the first byte of the network configuration has the integer value "1", 
	we will proceed to initialise the Ethernet shield and start the network
	services.
	
	Since Arduino's Ethernet library does not support DHCP, the IP address, 
	net mask, gateway and MAC address will have to be manually assigned. To do
	so, it will be necessary to connect to the Arduino board by the USB port, 
	and setting those values with the command "eth". 

	Once configured and activated, the board can be unplugged and the shield 
	can be placed on the main board. When the board is powered again, the 
	Ethernet interface should be available and waiting for connection.

	The network service provided is a listening port (\ref LISTENINGPORT) that, 
	once connected, will be used as the standard output.
	*/
  int i=0;
  byte mac[6];
  byte ip[4];
  byte gw[4];
  byte mask[4];
  if(EEPROM.read(EMNETCFOFFSET)==1) 
  { // Si el byte esta a 1, arrancamos la red
    EEPROM2string(EMNETCFOFFSET,6,mac);
    EEPROM2string(EMNETCFOFFSET+6,4,ip);
    EEPROM2string(EMNETCFOFFSET+10,4,gw);
    EEPROM2string(EMNETCFOFFSET+14,4,mask);  
    Ethernet.begin(mac,ip,gw,mask);
    Server server = Server(LISTENINGPORT);
    server.begin();  
    // Network is configured
	/// @todo The pins used by the ethernet shield should be disabled here
   //configPort( 0,"-",   "netrx"); 
   //configPort( 1,"-",   "nettx");

  }
  return -1;
}

/** Initializes the device. 
 This function is called when the first power supply is connected.
*/
void setup()
{
  int i;
  
  if (initNetwork()!=0) Serial.begin(9600);
  FOREACHLINK(i) emptyLink(i); 
  FOREACHPORT(i) portchanges[i]=0; // Ponemos a cero los contadores
  FOREACHVPORT(i) { vports[i][0]=0;vports[i][1]=0;vports[i][2]=0;vports[i][3]=0;vports[i][4]=0;vports[i][5]=0;}
  if(loadConfig()!=0) 
  { // Si la configuración es inválida cargamos la de por defecto
     loadDefaultConfig();   
  }
  // Iniciamos los puertos
  resetPorts();
}

/** Permanent loop.
 This is the main function of Arduino. This function is called again when
 the last call is ended.
*/
void loop()
{
	char b;
	byte i;

	// Leemos las marcas de tiempo y actualizamos valores 
	time = millis() / 1000;
	if (seconds != (time % DAYLENGHT)){ // Solamente actualizamos si ha cambiado
  		seconds = time % DAYLENGHT;
		if (seconds == 0) days++; 
		// Miramos si hay temporizadores:
		checkPendingTimers();
	}

	// El dispositivo Arduino estara permanentemente iterando este bucle,
	// este o no conectado a traves del puerto USB. En el caso de que haya
	// conexion establecida, se tendra en cuenta.

	// Serial.available() devuelve el numero de bytes acumulados en el buffer. 
	// Empleando un sistema de ancho fijo de los comandos facilitaremos en 
	// gran medida el procesado   
	while (Serial.available()>0) 
	{
		b = Serial.read();
		switch (b) {
		case -1: // Null char
		case 0:  // Null char
		case 10: // LF: No deberia suceder, salvo en consola emulada 
			break;
		case 13: // Final de linea
			processInstruction(instruction);
			inspos=0;
			break;
		case 8:  // Backspace
			if (inspos>0) 
			{
				inspos--;
				if (echo==1) Serial.print(b);
			}
			instruction[inspos]=0;
			break;
		default:
			if (inspos < BUFFERSIZE) 
			{
				if (echo==1) Serial.print(b);
				instruction[inspos]=b;
				inspos++;
				instruction[inspos]=0;
			}
			else
			{
				printf((char*)"E",(char*)"buffer", (char*)"full");
			}
		}
	}

	// Aparte de comprobar los comandos recibidos por Serial, miramos si 
	// hay algun cambio de estado en los puertos. 
	refreshPortStatus();

	// Tiempo de espera
	delay(DELAYCYCLE);
}




/// This function checks if there are active timers and triggers them if needed 
void checkPendingTimers(){
	int i=0;
//	byte value;
	for (i=0; i<DIGICOUNT; i++) 
	{
		// If the timer lenght is different than zero and the counter is active,
		if (t_len[i]>0 && t_count[i]>0)
		{
			t_count[i]--;
			if (t_count[i]==0) // If the timer counter reaches zero, trigger it. 
			{
  				raiseEvent4("I","tempo",portname[i],t_len[i]);
				if (portvalue[i]==LOW) // Forzamos el cambio de estado
 				{
  	  				triggerPortChange(i,portvalue[i],HIGH);
					portvalue[i]=HIGH;
 				}
     				else 
       				{
           	  			triggerPortChange(i,portvalue[i],LOW);
					portvalue[i]=LOW;
  				}
				delTimer(i);
			}
		}
	}
}

/** Deletes a timer 
 \param port Port ID
*/
void delTimer(byte port)
{
	t_len[port]=0;
	t_count[port]=0;
}

/** Creates a new timer
 \param port Port affected by the timer
 \param seconds Number of seconds to wait
*/
int addTimer(byte port, byte seconds) 
{
	t_len[port]=seconds;
	t_count[port]=0;
	raiseEvent4("I","tempo","len",seconds);
	return 0;
}



/// Check if any port has changed its value, and trigger an event if required.
void refreshPortStatus()
{
  int i=0;
  int k=0;
  int val;
  loops++;
  if (loops>=10) loops=0; // Cada 10 loops

  // refresca el estado de los puertos cada X milisegundos
  if(millis() - time < 10)
    return;
  else
    time = millis();


  FOREACHPORT(i) 
  {
    val = 0;
    
    if(ISOUTPUT(i))
    {
      if (ISDIGITAL(i)) 
        val=digitalRead(i);
      else
        val=portvalue[i];
      if(portvalue[i]!=val) triggerPortChange(i,portvalue[i],val);
    }
    else if (ISINPUT(i))
    {
      if (ISDIGITAL(i))
      { 
        // varias lecturas para minimizar problemas de ruido
        val=HIGH;
        for(k=0; k<10; k++)
        {
          if(digitalRead(i)!=HIGH)
          {
            val=LOW;
            break;
          }
          delay(10);
        }  

        
        if(portvalue[i]==LOW && val==HIGH)
        {
          triggerPortChange(i,portvalue[i], HIGH);
          portvalue[i]=HIGH;
        }
        else if (portvalue[i]==HIGH && val==LOW)
        {
          triggerPortChange(i,portvalue[i], LOW);
          portvalue[i]=LOW;
        }
      }
      
      if (ISANALOG(i))
      { 
		// Las entradas analogicas tienen un rango distinto
        val=analogRead(i-DIGICOUNT); 
        triggerPortChange(i,portvalue[i],val);
        portvalue[i]=val;
      }
      

      // Los puertos de entrada son susceptibles a sufrir ruido (verificar solo 
	  // durante los primeros 5s)
      if (loops==0 && time<5000) 
      {
        if (portchanges[i] >= MAXCHANGES) 
        {
		// puerto ruidoso si hubo mas de MAXCHANGES cambios por segundo
          porttype[i]='N'; // Desactivado
          printf((char*)"N",portname[i], (char*)"noisy");
        }
        portchanges[i]=0; // Ponemos a cero los contadores
      }  
    }   
    
  }
}

/** This function triggers an event if a port value has changed.
 \param port Port number
 \param ov Old value of the port
 \param nv New value of the port
*/
void triggerPortChange(byte port, byte ov, byte nv)
{
  byte i;
  if (ov==nv) return;
  
  portchanges[port]++;

  // Comprobamos rangos máximos y mínimos para los puertos de entrada analógicos
  if (ISINPUT(port) && ISANALOG(port))
  { // Si el valor supera el limite impuesto, y esta subiendo, disparar alarma
    i = port-DIGICOUNT;
    if (abs(nv-ov)>tolr[i])
	{
		raiseEvent4((char*)"N",portname[port], (char*)"newval",nv);
	}

    if ((amax[i]<1024) && (nv > amax[i]) && (nv>ov)){
      raiseEvent4((char*)"A",portname[port],(char*)"mxlvl",nv);
      setPortValue(alarmport,HIGH);
    }
    if ((wmax[i]<1024) && (nv > wmax[i]) && (nv >ov)){
      raiseEvent4((char*)"W",portname[port],(char*)"mxlvl",nv);
    }
    if ((amin[i]>0) && (nv < amin[i]) && (nv < ov)){
      raiseEvent4((char*)"A",portname[port],(char*)"mnlvl",nv);
      setPortValue(alarmport,HIGH);
    }
    if ((wmin[i]>0) && (nv < wmin[i]) && (nv < ov)){
      raiseEvent4((char*)"W",portname[port],(char*)"mnlvl",nv);
    }
  }
  if (ISINPUT(port) && ISDIGITAL(port))
  {
	// Si un puerto digital ha cambiado y tiene un temporizador asignado, le
	// asignamos al contador el valor correspondiente.
	if (t_len[port]>0 && t_count[port]==0 && nv==HIGH) t_count[port] = t_len[port];
	raiseEvent4((char*)"N",portname[port], (char*)"newval",nv);
  }

  for (i=0;i<MAXLINKS;i++)
  {
    if (links[i][0]==port) // Puerto enlazado
    {
      raiseEvent3((char*)"N",portname[links[i][1]],(char*)"trgrd");
      switch(links[i][2]) 
      {
        case 'd': // Directo: si A=1, B=1. Si A=0, B=0. Si A=65%, B=65%
          setPortValue(links[i][1],nv);
          break;
        case 'i': // Inverso: si A=1, B=0. Si A=0, B=1. Si A=65%, B=35%
			if (nv==LOW) 
				setPortValue(links[i][1],HIGH);
			else if (nv==HIGH)
				setPortValue(links[i][1],LOW);
			/// @todo Activate inverted link for analog ports
			//else
			//	setPortValue(links[i][1],255-nv);
          break;
        case 'p': // Pulso: 0->1 o 1->0 cuando el puerto pasa a HIGH
          if (ISDIGITAL(port) && nv==HIGH) 
          {
            if (portvalue[links[i][1]]==LOW)
              setPortValue(links[i][1],HIGH);
            else
              setPortValue(links[i][1],LOW);
          }
          break;
        case 'f': // Fall: 0->1 o 1->0 cuando el puerto pasa a LOW
          if (ISDIGITAL(port) && nv==LOW) 
          {
            if (portvalue[links[i][1]]==LOW)
              setPortValue(links[i][1],HIGH);
            else
              setPortValue(links[i][1],LOW);
          }
          break;
          
        default:
          raiseEvent3((char*)"E",(char*)"board",(char*)"invalid link type");
          break;

      }
    }
  }
}


/// Send the list of links created to the standard output
void listLinks()
{
  int i=0;
  int j=0;
  int a=0;

  FOREACHLINK(i)
  {
	// Los "slots" vacíos se almacenan como enlace "0-0"
    if ((links[i][0]!=0) && (links[i][1]!=0)) 
    {
      Serial.print("I: l");
      printnum(i);
      Serial.print(" ");
      if (links[i][0]<=PINCOUNT) // standard port link
      {
         Serial.print(portname[links[i][0]]);
      }
      else // virtual port link
      {
        Serial.print("V");
        printnum(links[i][0]-PINCOUNT);
      }    
      Serial.print("  ");
      Serial.print(portname[links[i][1]]);
      Serial.print(" ");
      Serial.println(links[i][2]);        
      a++;
    }

    // Formato de salida:
    // 01234567890123456789012345 (byte nº)
    // I: l0001 port1 port2 type
  }
  if (a==0) raiseEvent3((char*)"W",(char*)"board",(char*)"no links");
}

/// Send the list of ports to the standard output, including type and value
void listPorts()
{
  int i;
  int j;
  int perc;
  char type[7];
  char value[7];
  FOREACHPORT(i)
  {
    for (j=0;j<6;j++){type[j]='-';value[j]=' ';}
    type[6]=0;value[6]=0;
//    Serial.print("I: ");
//    Serial.print(portname[i]);
    if (ISDIGITAL(i))
    {
      type[0]='D';
      if (ISINPUT(i))
		type[1]='I';
      else  
		type[1]='O';         

      if (digitalRead(i) == HIGH) {
        value[0]='O'; value[1]='N'; value[2]=' ';
      } else {
		value[0]='O'; value[1]='F'; value[2]='F';
      }
//      Serial.print(" ");
//      printnum(i);
    }
    else if (ISANALOG(i))
    {
      type[0]='A';
      if (ISINPUT(i)) {
		type[1]='I';
		itoan(analogRead(i-DIGICOUNT),value,5);
		value[5]=' ';
      } else {  
		type[1]='O';
		itoan(portvalue[i],value,5);
		value[5]=' ';
      }

/*      Serial.print(" A");
      if (ISINPUT(i)) 
      {
        Serial.print("I--- ");
        printnum(analogRead(i-DIGICOUNT));
        Serial.print(" ");
        printnum(i);
        Serial.print(" (");
        printnum(amin[i-DIGICOUNT]);
        Serial.print(",");
        printnum(wmin[i-DIGICOUNT]);
        Serial.print(",");
        printnum(wmax[i-DIGICOUNT]);
        Serial.print(",");
        printnum(amax[i-DIGICOUNT]);
        Serial.print(")");
      }
      else 
      {
        Serial.print("O--- ");         
        printnum(portvalue[i]);
        Serial.print(" ");
        printnum(i);
      } */
    }
    else if(ISPULSE(i))
    {
		type[0]='P';
      if (ISINPUT(i))
        type[1]='I'; 
      else
        type[1]='O';
//      printnum(i);
    }
    else
    {
      type[0]='X'; 
    }
   //printf("I",portname[i],type,value,i);
   //raiseEvent4("I",portname[i],type,i);
   Serial.print("I: ");
   Serial.print(portname[i]);
   Serial.print(" ");
   Serial.print(type);
   Serial.print(" ");
   Serial.print(value);
   //Serial.print(" ");
   printnum(i);
   Serial.println("");
//    Serial.println();
  }    
}

/** Links two ports
 \param port1 Trigger port (usually input)
 \param port2 Triggered port (usually output)
 \return 0 if everything went right, otherwise -1
*/
int addLink(byte port1, byte port2)
{
	int l;
	int slot=-1;

	if ((port1==-1) || (port2==-1)) return -2;

	if ((ISINPUT(port1) && ISOUTPUT(port2)) || ISVIRTUAL(port1))
	{
//    for (l=0;l<MAXLINKS;l++)
		FOREACHLINK(l)
		{
			if (l<MAXLINKS) {
				if((links[l][0]==port1) && (links[l][1]==port2))
				{
					raiseEvent3((char*)"E",(char*)"board", (char*)"dupln");
					return -2;
				} 
				else if((links[l][0]==0) && (links[l][1]==0))
				{
					slot = l; // "slot" contiene el ultimo par libre
				}
			}
		}
		if (slot==-1) // No hay slots libres
		{
			raiseEvent3((char*)"E",(char*)"board",(char*)"maxln");
			return -3;
		}
		links[slot][0] = port1;
		links[slot][1] = port2;
		links[slot][2] = 'd'; // Por defecto, tipo "d"
	}
	else
	{
		raiseEvent3((char*)"E",(char*)"board",(char*)"invld");
		return -1;
	}
	return 0;
}
/** Delete the link between two ports.
 \param port1 Trigger port 
 \param port2 Triggered port 
 \return 0 if everything went right, otherwise -1
*/
int delLink(byte port1, byte port2)
{
  int l;
  if ((port1==-1) || (port2==-1)) return -2;
  for (l=0;l<MAXLINKS;l++)
  {
    if((links[l][0]==port1) && (links[l][1]==port2))
    {
      links[l][0]=0;
      links[l][1]=0;
      links[l][2]=0;
      return 0;
    } 
  }
  return -1; // Enlace no encontrado
}
/** \page VPorts Virtual Ports
 The virtual ports are events that can be used as regular ports, linked to 
 output ports to change their values to HIGH when the events start, bringing 
 it to LOW when it's ended. 

 Virtual ports don't have a configuration register, as the regular ports have.
 This configuration is part of the name of the virtual port; the first byte
 is showing the type of event (P for pulse), and the rest of the name contains
 the information required.
*/

/** Create a virtual port
 \param name code for the port 
 \return 0 if everything went right, otherwise -1
*/
int addVirtualPort(char *name)
{
	int i;
	FOREACHVPORT(i)
	{
		if (vports[i][0]==0) { // Empty! Add it here
			Serial.println("D: adding virtual port");
 			Serial.println(name);
			if (name[0]=='P') // First byte designates a pulse
			{
				vports[i][0]='P';
				vports[i][1]=h2d(name[1],name[2]);
				vports[i][2]=h2d(name[3],name[4]);
				vports[i][3]=h2d(name[5],name[6]);
				vports[i][4]=h2d(name[7],name[8]);
				vports[i][5]=0;
				vports[i][6]=0;
			}
			else
			{
				vports[i][0]=name[0];
				vports[i][1]=name[1];
				vports[i][2]=name[2];
				vports[i][3]=name[3];
	  			vports[i][4]=name[4];
				vports[i][6]=0;

			}
			Serial.print("D: vport V");
			printnum(i);
			Serial.println(" added");
			return 0;
		}
		else
		{
			Serial.print("D: slot occuppied by ");
			Serial.println(vports[i]);
			if (vports[i][0]==name[0]) { // Hey! it might be stored already!
				//! @todo Prevent storing repeated virtual ports
			}
		}
	}
	return -1;
}
/** List virtual ports
*/
void listVirtualPorts()
{
	int i;
	int j;
	byte c;
	FOREACHVPORT(i)
	{
		Serial.print("I: V");
		printnum(i);
		if (vports[i][0]==0) // Nothing here, ignore it!
		{
			Serial.println(" ----------");		 	
		} 
		else if (vports[i][0]=='P') // Pulse. Printing in hex!
		{
			Serial.print(" P:");
			for (j=0;j<4;j++) {
			c=vports[i][j+1];
//			Serial.print("-"); 
			if (c < 0x10) Serial.print("0");
  			Serial.print(c,HEX);
			}
			Serial.println(" ");
		}
		else
		{
			Serial.print(" ");
			Serial.println(vports[i]);
		}
	}
}

/** Get the specified port index
 \param label Name of the port
 \return The port's index, or -1 if not found 
*/
int getPortId(char *label)
{
  int i;
  if (label[0]=='V') // it is a virtual port
  {
     label[0]='0';
     i = atoi(label);
     Serial.print("D: searching virtual port ");
     Serial.println(i,DEC);
     if (vports[i][0]!=0)
       return i + PINCOUNT;
     else
       return -1;
  }
  
  FOREACHPORT(i)
  {
    if (strcmp6(portname[i],label)) 
    {
      return i;
    }
  }
  //! @todo get Virtual Port's ID
  
  
  raiseEvent3((char*)"E",(char*)"board invalid port", label);
  return -1;
}

/** Set a value for the specified port
 \param i Port index (see \ref getPortId)
 \param value New value
 \return 0 if the value could be assigned, other if not.
*/
int setPortValue(int i, int value)
{

//  if(i=-1) return -1;
    
  if (ISINPUT(i))
  {
    raiseEvent3((char*)"E",portname[i],(char*)"read only port");
    return 1;
  }
  
  if (ISDIGITAL(i))
  {
      digitalWrite(i,value);
      portvalue[i]=value;
  } 
  else if (ISANALOG(i))
  {
    analogWrite(i, value);
    portvalue[i]=value;
    raiseEvent4((char*)"D",portname[i],(char*)"changed to",value);
  }
  else if(ISVIRTUAL(i)) 
  {
    triggerPortChange(i,LOW,HIGH);
  }
  else
  {
    raiseEvent3((char*)"E",portname[i],(char*)"invalid port type");
    return 1;
  }
  
  raiseEvent3((char*)"N",portname[i],(char*)"updated");
}


/** Set the Ethernet state
 \param state Status of the network: 1=ON, 0=OFF
*/
int ethSetState(byte state)
{
  if (state==HIGH) {
    EEPROM.write(EMNETCFOFFSET,1);
  } else {
    EEPROM.write(EMNETCFOFFSET,0);
  }
}
/** Save the MAC into the EEPROM
 \param macb1 Byte 1 of MAC Address
 \param macb2 Byte 2 of MAC Address
 \param macb3 Byte 3 of MAC Address
 \param macb4 Byte 4 of MAC Address
 \param macb5 Byte 5 of MAC Address
 \param macb6 Byte 6 of MAC Address
*/
int ethSetMAC (byte macb1, byte macb2, byte macb3, \
	byte macb4, byte macb5, byte macb6)
{
   EEPROM.write(EMNETCFOFFSET+1,macb1);
   EEPROM.write(EMNETCFOFFSET+2,macb2);
   EEPROM.write(EMNETCFOFFSET+3,macb3);
   EEPROM.write(EMNETCFOFFSET+4,macb4);
   EEPROM.write(EMNETCFOFFSET+5,macb5);
   EEPROM.write(EMNETCFOFFSET+6,macb6);
}
/** Save the IP address into the EEPROM
 \param ipb1 Byte 1 of IP Address
 \param ipb2 Byte 2 of IP Address
 \param ipb3 Byte 3 of IP Address
 \param ipb4 Byte 4 of IP Address
*/
int ethSetIP (byte ipb1, byte ipb2, byte ipb3, byte ipb4)
{
   EEPROM.write(EMNETCFOFFSET+7,ipb1);
   EEPROM.write(EMNETCFOFFSET+8,ipb2);
   EEPROM.write(EMNETCFOFFSET+9,ipb3);
   EEPROM.write(EMNETCFOFFSET+10,ipb4);
}
/** Save the IP address of the gateway into the EEPROM
 \param ipb1 Byte 1 of IP Address
 \param ipb2 Byte 2 of IP Address
 \param ipb3 Byte 3 of IP Address
 \param ipb4 Byte 4 of IP Address
*/
int ethSetGW (byte ipb1, byte ipb2, byte ipb3, byte ipb4)
{
   EEPROM.write(EMNETCFOFFSET+11,ipb1);
   EEPROM.write(EMNETCFOFFSET+12,ipb2);
   EEPROM.write(EMNETCFOFFSET+13,ipb3);
   EEPROM.write(EMNETCFOFFSET+14,ipb4);
}
/** Save the network mask into the EEPROM
 \param ipb1 Byte 1 of mask
 \param ipb2 Byte 2 of mask
 \param ipb3 Byte 3 of mask
 \param ipb4 Byte 4 of mask
*/
int ethSetNetmask (byte ipb1, byte ipb2, byte ipb3, byte ipb4)
{
   EEPROM.write(EMNETCFOFFSET+15,ipb1);
   EEPROM.write(EMNETCFOFFSET+16,ipb2);
   EEPROM.write(EMNETCFOFFSET+17,ipb3);
   EEPROM.write(EMNETCFOFFSET+18,ipb4);
}
/** Sub-procedure for dealing with Ethernet board
 \param cmd Command
 \param larg Long argument
 \return 
 @todo Remove the second parameter
*/
int ethControl(char *cmd, char *larg)
{
    if (cmd[4]=='u' && cmd[5]=='p') {      // Activating ethernet
      ethSetState(HIGH);
    }
    else if (cmd[4]=='s' && cmd[5]=='t') { // Status of the ethernet
      // Under construction
    }
    else if (cmd[4]=='m' && cmd[5]=='c') { // setting MAC address
      ethSetMAC(h2d(larg[0],larg[1]),h2d(larg[2],larg[3]),h2d(larg[4],larg[5]),\
		h2d(larg[6],larg[7]), h2d(larg[8],larg[9]), h2d(larg[10],larg[11]));
    }
    else if (cmd[4]=='i' && cmd[5]=='p') { // setting IP
      ethSetIP(h2d(larg[0],larg[1]), h2d(larg[2],larg[3]),\
		h2d(larg[4],larg[5]), h2d(larg[6],larg[7]));
    } 
    else if (cmd[4]=='g' && cmd[5]=='w') { // setting gateway
      ethSetGW(h2d(larg[0],larg[1]), h2d(larg[2],larg[3]),\
		h2d(larg[4],larg[5]), h2d(larg[6],larg[7]));
    }
    else if (cmd[4]=='n' && cmd[5]=='m') { // setting netmask
      ethSetNetmask(h2d(larg[0],larg[1]), h2d(larg[2],larg[3]),\
		h2d(larg[4],larg[5]), h2d(larg[6],larg[7]));
    }
    else if (cmd[4]=='o' && cmd[5]=='f') { // deactivating ethernet
      ethSetState(LOW);
    }
    return 0;
}


/** Executes the specified command
 \param cmd The command string (max 20Bytes)
 \return 0 if the operation was successful, other if not
*/
int processInstruction(char *cmd)
{
  int i;
  int j;
  char arg1[6]; // First argument (if specified)
  char arg2[6]; // Second argument (if specified)
  char larg[BUFFERSIZE-5]; // Bytes from 5-20, both arguments and space
  int value=0;
  /* All the commands will follow a similar structure:
   * 012345678901234 <-byte no.
     cmd arg01 arg02
     cfg portn di
     lbl portn alias
     lnk portn portm
     wmx portn value
     wmn portn value
     set portn value
   */
  GETARG1(cmd,arg1);
  GETARG2(cmd,arg2);

  /// Supported commands:
  if (SPELL3(cmd,'l','s','t'))         /// - lst: List configured ports
  {
    listPorts();
    return 0;
  }
  else if (SPELL3(cmd,'e','c','h'))    /// - echo: (de)activates local echo
  {
    if (echo==0) {raiseEvent3((char*)"N",(char*)"board",(char*)"echo on"); echo=1;} 
    else {raiseEvent3((char*)"N",(char*)"board",(char*)"echo off"); echo=0;}
    return 0;
  }
  else if (SPELL3(cmd,'d','e','b'))    /// - debug: (de)activates debug mode
  {
    if (debug==0) {raiseEvent3((char*)"N",(char*)"board",(char*)"debug on"); debug=1;} 
    else {raiseEvent3((char*)"N",(char*)"board",(char*)"debug off"); debug=0;}
    return 0;
  }
  else if (SPELL3(cmd,'d','e','f'))    /// - default: load default configuration
  {
    loadDefaultConfig();
    resetPorts();      
    raiseEvent3((char*)"N",(char*)"board",(char*)"cfdef");
  }
  else if (SPELL3(cmd,'l','o','a'))    /// - load: load stored configuration
  {
    if (loadConfig()==0) raiseEvent3((char*)"N",(char*)"board",(char*)"cfloa");
    return 0;
  }
  else if (SPELL3(cmd,'s','a','v'))    /// - save: save configuration to EEPROM
  {
    if (saveConfig()==0) raiseEvent3((char*)"N",(char*)"board",(char*)"cfsav");
    return 0;
  }
  else if (SPELL3(cmd,'c','f','g'))    /// - cfg: configures the specified port
  {
    i = getPortId(arg1);      
    if(configPort(i,arg2,NULL)==0) raiseEvent3((char*)"N",portname[i],(char*)"cfgok");
    resetPorts();
  }
  else if (SPELL3(cmd,'l','b','l'))    /// - lbl: set an alias to a port
  {
    i = getPortId(arg1);      
    if(configPort(i,NULL,arg2)==0) raiseEvent3((char*)"N",portname[i],(char*)"cfgok");
  }
  else if (SPELL3(cmd,'r','e','s'))    /// - reset: clear the EEPROM
  {  
    // Usaremos solo caracteres imprimibles para mejor trazabilidad
    for (i=0;i<1000;i++) EEPROM.write(i,' '); 
    raiseEvent3((char*)"N",(char*)"board",(char*)"memcl");
  }
  else if (SPELL3(cmd,'m','a','p'))    /// - map: print the \ref EEPROM
  {  
    if (cmd[4]=='h')
      printMap(2);
    else
      printMap(1);
  }
  else if (SPELL3(cmd,'v','e','r'))    /// - ver: display the current version
  { 
    raiseEvent3((char*)"I",(char*)"board",(char*)VERSION);
  }
  else if (SPELL3(cmd,'n','a','m'))    /// - name: print the board name
  { 
    printBoardName();
  }
  else if (SPELL3(cmd,'s','n','m'))    /// - snm: set a new name for the board
  { 
    saveBoardName(arg1);
    raiseEvent3((char*)"N",(char*)"board",(char*)"operation succeeded");
  }
  else if (SPELL3(cmd,'w','m','x'))    /// - wmx: set max warning for a analog port
  {  
    i = getPortId(arg1) - DIGICOUNT;
    wmax[i] = atoi(arg2);
    raiseEvent4((char*)"N",arg1,(char*)"maxwarning",wmax[i]);
  }
  else if (SPELL3(cmd,'w','m','n'))    /// - wmn: set min warning for a analog port
  {  
    i = getPortId(arg1) - DIGICOUNT;
    wmin[i] = atoi(arg2);
    raiseEvent4((char*)"N",arg1,(char*)"minwarning",wmin[i]);
  }
  else if (SPELL3(cmd,'a','m','x'))    /// - amx: set max alarm for a analog port
  {  
    i = getPortId(arg1) - DIGICOUNT;
    amax[i] = atoi(arg2);
    raiseEvent4((char*)"N",arg1,(char*)"maxalarm",amax[i]);
  }
  else if (SPELL3(cmd,'a','m','n'))    /// - amn: set min alarm for a analog port
  {  
    i = getPortId(arg1) - DIGICOUNT;
    amin[i] = atoi(arg2);
    raiseEvent4((char*)"N",arg1,(char*)"minalarm",amin[i]);
  }
  else if (SPELL3(cmd,'t','o','l'))    /// - tol: set tolerance for an analog port
  {  
    i = getPortId(arg1) - DIGICOUNT;
    tolr[i] = atoi(arg2);
    raiseEvent4((char*)"N",arg1,(char*)"tolerance",tolr[i]);
  }
  else if (SPELL3(cmd,'s','e','t'))    /// - set: set a value to an output port
  {
    i = getPortId(arg1);
    if (arg2[1]=='n')
      setPortValue(i,HIGH);
    else if (arg2[1]=='f')
      setPortValue(i,LOW);
    else
      setPortValue(i,atoi(arg2));
  } 
  else if (SPELL3(cmd,'l','n','k'))    /// - lnk: link two ports
  {
    i = getPortId(arg1);
    j = getPortId(arg2);
    if(addLink(i,j)==0) raiseEvent3((char*)"N",arg1,(char*)"linked");
    return 0;
  } 
  else if (SPELL3(cmd,'u','n','l'))    /// - unl: unlink two ports
  {
    i = getPortId(arg1);
    j = getPortId(arg2);
    if(delLink(i,j)==0) raiseEvent3((char*)"N",arg1,(char*)"unlinked");
    return 0;
  } 
  else if (SPELL3(cmd,'l','l','n'))    /// - lln: list the current links
  {
    listLinks();
    return 0;
  } 
  else if (SPELL3(cmd,'l','c','f'))    /// - lcf: configure the specified link
  {
    i = atoi(arg1);
    if (i<MAXLINKS) 
    {
      raiseEvent4((char*)"N",(char*)"board",(char*)"configuring link",i);
      links[i][2]=arg2[0]; // Provisional. Por ahora empleamos un solo byte
      return 0;
    }
    else 
    {
       raiseEvent3((char*)"E",(char*)"board",(char*)"invalid link");
       return -1;
    }
  } 
  else if (SPELL3(cmd,'a','v','p'))    /// - avp: add virtual port (EXPERIMENTAL)
  {
    for (i=4;i<BUFFERSIZE;i++){
      larg[i-4]=cmd[i];
    }
    if(addVirtualPort(larg)==0) 
		raiseEvent3((char*)"N",(char*)"vport",(char*)"created");
	else
		raiseEvent3((char*)"E",(char*)"vport",(char*)"not created");
  }
  else if (SPELL3(cmd,'l','v','p'))    /// - lvp: list virtual ports (EXPERIMENTAL)
  {
	listVirtualPorts();
  }
  else if (SPELL3(cmd,'u','p','t'))    /// - upt: print uptime
  {
  	raiseEvent4((char*)"I",(char*)"uptime",(char*)"seconds",seconds);
  	raiseEvent4((char*)"I",(char*)"uptime",(char*)"days",days);
  }
  else if (SPELL3(cmd,'t','i','m'))    /// - tim: add timer (EXPERIMENTAL)
  {
    i = getPortId(arg1);
    if(addTimer(i,atoi(arg2))==0)
      raiseEvent3((char*)"I",(char*)"timer", (char*)"added");
    else
      raiseEvent3((char*)"E",(char*)"timer", (char*)"failed");
  }
  else if (SPELL3(cmd,'t','s','t'))    /// - tst: perform a board test
  {
    raiseEvent3((char*)"I",(char*)"board", (char*)"test launched");
  }
  else if (SPELL3(cmd,'e','t','h'))    /// - eth: ethernet functions
  {
    //eth mc EFEFEFEFEFEF
    //0123456789012345678
    for (i=5;i<BUFFERSIZE;i++){
      larg[i-5]=cmd[i];
    }
    ethControl(cmd, larg);
  }
  else
  {   
    raiseEvent3((char*)"E",(char*)"board",(char*)"syntax error");
    
    /// @todo Remove this debugging information in RC
	if (debug==1) 
	{
		Serial.println(cmd);
		Serial.println(" bytes received:");
		for (i=0;i<inspos;i++)
		{
		  Serial.print(i,DEC);
		  Serial.print(" - ");
		  Serial.println(instruction[i],HEX);
		}
	}
    return 1;
  }
}


/** Print the contents of EEPROM (debugging only). See \ref EEPROM Map
 \param type 1=Normal, 2=Hex
*/
void printMap(int type)
{
  int i;
  int j;
  int p;
  int b;

  // Usaremos solo caracteres imprimibles para mejor trazabilidad (0 - 999)
	if (type==1) // Decimal
	{
		for (i=0;i<20;i++) // Filas 
		{
			for (j=0;j<50;j++)  // Columnas
			{
				p = i * 50 + j; 
				b = EEPROM.read(p);
				if ((b>31) && (b<126))
				  Serial.print(b,BYTE);
				else
				  Serial.print("?");
				// Cada 5 Bytes un separador
				if (((j+1) % 5) == 0)  Serial.print("·"); 
			}
		Serial.println();
		}
	}
	if (type==2) // Hex
	{
		Serial.println("I: 0 1 2 3 4 5 6 7 8 9. 0 1 2 3 4 5 6 7 8 9. 0 1 2 3 4");
		for (i=0;i<40;i++) // Filas 
		{

			for (j=0;j<25;j++) 
			{
				p = i * 25 + j; 
				b = EEPROM.read(p);
				if(b>0)
				  Serial.print(b,HEX);
				else
				  Serial.print("00");
				// Cada 10 Bytes un separador
				if (((j+1) % 10) == 0)  Serial.print("·"); 
			}
		Serial.println();
		}
	}
}

// NEC protocol time constants 
#define NEC_BIT_START 4000			//!< Starting bit timeout
#define NEC_BIT_0 1400				//!< Code for binary 0
#define NEC_BIT_1 420				//!< Code for binary 1
#define NEC_START_TIMEOUT 10000		//!< Start timeout
#define NEC_BIT_TIMEOUT 3000		//!< bit timeout
#define NEC_REPEAT_S 20000			//!< Repeat timeot
#define NEC_REPEAT_TIMEOUT_S 70000	//!< Repeat timeout s 
#define NEC_REPEAT 2000				//!< Repeat
#define NEC_REPEAT_TIMEOUT 110000	//!< Repeat timeout2

/** Start reading from the specified buffer-type port
*/
void processIR(){
  int bitStart, bitTime, bitValue;
  int in;
  int vpn=0;

  // Interrupt deactivated
  if (pulseport==2) in=0;
  if (pulseport==3) in=1;
  detachInterrupt(in);
  
  
  bitStart=pulseIn(pulseport, HIGH, NEC_START_TIMEOUT);
  if(bitStart > NEC_BIT_START){
    // Reading 4 bytes
    int i, j;
    for(i=0; i<4; i++){
      for(j=0; j<8; j++){
        bitTime=pulseIn(pulseport, HIGH, NEC_BIT_TIMEOUT);
        if(bitTime > NEC_BIT_1 && bitTime < NEC_BIT_0){
          // It's a 1
          bitSet(codeIR[i], j);
        }
        else{
        	 // It's a 0
          bitClear(codeIR[i], j);
        }
      }
    }
    
    //Formatting information:
    Serial.print("N: pulse ");
    for(i=0; i<4; i++){
      if(codeIR[i] < 0x10){
        Serial.print("0");
        Serial.print(codeIR[i], HEX);
      }
      else{
        Serial.print(codeIR[i], HEX);
      }
    }
    Serial.println();
    //! @todo format code here
	FOREACHVPORT(i){
           if ((vports[i][0]=='P') && (vports[i][1]==codeIR[0]) && (vports[i][2]==codeIR[1]) && (vports[i][3]==codeIR[2]) && (vports[i][4]==codeIR[3]))
           { 
                vpn = i+PINCOUNT;
           }
	}   

    if(vpn!=0) triggerPortChange(vpn,LOW,HIGH);
    // Verify if the pulse is repeating
    bitStart=pulseIn(pulseport, HIGH, NEC_REPEAT_TIMEOUT_S);
    if(bitStart > NEC_REPEAT){
      Serial.println("N: repeat");
      // Verify again
      while(pulseIn(pulseport, HIGH, NEC_REPEAT_TIMEOUT) > NEC_REPEAT){
        Serial.println("N: repeat");
      }
    }    
  }
  if(vpn!=0) triggerPortChange(vpn,HIGH,LOW);  
  // Re-activate interrupt
  attachInterrupt(in, processIR, CHANGE);
  
}
